package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/driver/desktop"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/ncruces/zenity"
)

type ScriptManager struct {
	scriptsDir string
	historyDir string
	httpPort   string
}

type ExecutionRecord struct {
	ScriptName  string    `json:"script_name"`
	ExecutedAt  time.Time `json:"executed_at"`
	Output      string    `json:"output"`
	Error       string    `json:"error,omitempty"`
	ExitCode    int       `json:"exit_code"`
	ClientIP    string    `json:"client_ip,omitempty"` // 客户端IP地址
	ExecuteType string    `json:"execute_type"`        // 执行类型：GUI或HTTP
}

// HoverButton is a button that shows a hand cursor when hovered, improving UX.
type HoverButton struct {
	widget.Button
}

// NewHoverButton creates a new HoverButton with the given text, icon and tap handler.
func NewHoverButton(label string, icon fyne.Resource, tapped func()) *HoverButton {
	hb := &HoverButton{}
	hb.Text = label
	hb.Icon = icon
	hb.OnTapped = tapped
	hb.ExtendBaseWidget(hb)
	return hb
}

// Ensure HoverButton implements desktop.Cursorable.
var _ desktop.Cursorable = (*HoverButton)(nil)

// Cursor returns the cursor used when the pointer hovers over the button.
func (b *HoverButton) Cursor() desktop.Cursor {
	return desktop.PointerCursor
}

func NewScriptManager() *ScriptManager {
	sm := &ScriptManager{
		scriptsDir: "scripts",
		historyDir: "history",
		httpPort:   ":8099",
	}

	// 创建必要的目录
	os.MkdirAll(sm.scriptsDir, 0755)
	os.MkdirAll(sm.historyDir, 0755)

	return sm
}

// 获取客户端真实IP地址
func getClientIP(r *http.Request) string {
	// 首先检查X-Forwarded-For头
	if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
		// X-Forwarded-For可能包含多个IP，取第一个
		if ips := strings.Split(xff, ","); len(ips) > 0 {
			return strings.TrimSpace(ips[0])
		}
	}

	// 检查X-Real-IP头
	if xri := r.Header.Get("X-Real-IP"); xri != "" {
		return xri
	}

	// 检查X-Forwarded头
	if xf := r.Header.Get("X-Forwarded"); xf != "" {
		return xf
	}

	// 最后使用RemoteAddr
	if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
		return ip
	}

	return r.RemoteAddr
}

// 获取执行类型的显示文本
func getExecuteTypeDisplay(executeType string) string {
	switch executeType {
	case "HTTP":
		return "🌐 远程调用"
	case "GUI":
		return "🖱️ 界面操作"
	default:
		return executeType
	}
}

func (sm *ScriptManager) getScripts() []string {
	files, err := os.ReadDir(sm.scriptsDir)
	if err != nil {
		return []string{}
	}

	// 支持的脚本类型
	supportedExts := []string{".bat", ".sh", ".ps1", ".cmd"}

	var scripts []string
	for _, file := range files {
		if !file.IsDir() {
			fileName := file.Name()
			for _, ext := range supportedExts {
				if strings.HasSuffix(strings.ToLower(fileName), ext) {
					scripts = append(scripts, fileName)
					break
				}
			}
		}
	}
	return scripts
}

func (sm *ScriptManager) getDefaultScript() string {

	scripts := sm.getScripts()
	for _, script := range scripts {
		if strings.Contains(script, ".default.") {
			return script
		}
	}
	return ""
}

func (sm *ScriptManager) setDefaultScript(scriptName string) error {
	// 首先取消当前默认脚本
	currentDefault := sm.getDefaultScript()
	if currentDefault != "" {
		newName := strings.Replace(currentDefault, ".default.", ".", 1)
		oldPath := filepath.Join(sm.scriptsDir, currentDefault)
		newPath := filepath.Join(sm.scriptsDir, newName)
		os.Rename(oldPath, newPath)
	}

	// 设置新的默认脚本
	if scriptName != "" {
		oldPath := filepath.Join(sm.scriptsDir, scriptName)

		// 获取文件扩展名
		ext := filepath.Ext(scriptName)
		nameWithoutExt := strings.TrimSuffix(scriptName, ext)

		// 生成新的默认脚本名
		newName := nameWithoutExt + ".default" + ext
		newPath := filepath.Join(sm.scriptsDir, newName)
		return os.Rename(oldPath, newPath)
	}

	return nil
}

func (sm *ScriptManager) executeScript(scriptName string) (*ExecutionRecord, error) {
	return sm.executeScriptWithContext(scriptName, "GUI", "")
}

func (sm *ScriptManager) executeScriptWithContext(scriptName, executeType, clientIP string) (*ExecutionRecord, error) {
	scriptPath := filepath.Join(sm.scriptsDir, scriptName)

	record := &ExecutionRecord{
		ScriptName:  scriptName,
		ExecutedAt:  time.Now(),
		ExecuteType: executeType,
		ClientIP:    clientIP,
	}

	// 根据文件扩展名选择执行方式
	ext := strings.ToLower(filepath.Ext(scriptName))
	var cmd *exec.Cmd

	switch ext {
	case ".bat", ".cmd":
		// Windows批处理文件
		cmd = exec.Command("cmd", "/c", scriptPath)
	case ".sh":
		// Unix/Linux/Mac Shell脚本
		// 首先尝试使用bash，如果失败则使用sh
		if _, err := exec.LookPath("bash"); err == nil {
			cmd = exec.Command("bash", scriptPath)
		} else {
			cmd = exec.Command("sh", scriptPath)
		}
	case ".ps1":
		// PowerShell脚本
		cmd = exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-File", scriptPath)
	default:
		// 默认尝试直接执行（适用于有执行权限的脚本）
		cmd = exec.Command(scriptPath)
	}

	output, err := cmd.CombinedOutput()
	record.Output = string(output)

	if err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			record.ExitCode = exitError.ExitCode()
		} else {
			record.ExitCode = 1
		}
		record.Error = err.Error()
	} else {
		record.ExitCode = 0
	}

	sm.saveExecutionRecord(record)
	return record, err
}

func (sm *ScriptManager) saveExecutionRecord(record *ExecutionRecord) {
	// 获取脚本名称（去掉扩展名和.default部分）
	scriptBaseName := record.ScriptName

	// 先去掉.default部分
	scriptBaseName = strings.Replace(scriptBaseName, ".default", "", 1)

	// 再去掉文件扩展名
	ext := filepath.Ext(scriptBaseName)
	scriptBaseName = strings.TrimSuffix(scriptBaseName, ext)

	// 创建脚本专用目录
	scriptDir := filepath.Join(sm.historyDir, scriptBaseName)
	os.MkdirAll(scriptDir, 0755)

	// 生成文件名：时间戳_脚本名.json（包含毫秒以避免重复）
	timestamp := record.ExecutedAt.Format("20060102_150405.000")
	filename := fmt.Sprintf("%s_%s.json", timestamp, scriptBaseName)
	filePath := filepath.Join(scriptDir, filename)

	data, _ := json.MarshalIndent(record, "", "  ")
	err := os.WriteFile(filePath, data, 0644)
	if err != nil {
		log.Printf("Failed to save execution record: %v", err)
		return
	}

	log.Printf("Execution record saved: %s (Type: %s, IP: %s)", filePath, record.ExecuteType, record.ClientIP)

	// 惰性删除：检查该脚本目录下的记录数量，如果超过100条则删除最旧的记录
	sm.cleanupExecutionRecords(scriptDir)
}

// 清理执行记录，保持最多100条记录
func (sm *ScriptManager) cleanupExecutionRecords(scriptDir string) {
	files, err := os.ReadDir(scriptDir)
	if err != nil {
		return
	}

	// 筛选出.json文件
	var jsonFiles []os.DirEntry
	for _, file := range files {
		if !file.IsDir() && strings.HasSuffix(file.Name(), ".json") {
			jsonFiles = append(jsonFiles, file)
		}
	}

	// 如果记录数量不超过100条，不需要清理
	if len(jsonFiles) <= 100 {
		return
	}

	// 按文件名排序（文件名包含时间戳，可以直接字符串排序）
	sort.Slice(jsonFiles, func(i, j int) bool {
		return jsonFiles[i].Name() < jsonFiles[j].Name()
	})

	// 删除最旧的记录，保留最新的100条
	recordsToDelete := len(jsonFiles) - 100
	for i := 0; i < recordsToDelete; i++ {
		filePath := filepath.Join(scriptDir, jsonFiles[i].Name())
		if err := os.Remove(filePath); err == nil {
			log.Printf("Cleaned up old execution record: %s", filePath)
		}
	}
}

func (sm *ScriptManager) startHTTPServer() {
	http.HandleFunc("/run_default", func(w http.ResponseWriter, r *http.Request) {
		defaultScript := sm.getDefaultScript()
		if defaultScript == "" {
			http.Error(w, "No default script set", http.StatusNotFound)
			return
		}

		// 获取客户端IP地址
		clientIP := getClientIP(r)

		// 使用HTTP执行类型和客户端IP执行脚本
		_, err := sm.executeScriptWithContext(defaultScript, "HTTP", clientIP)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
		}

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("success"))

		// w.Header().Set("Content-Type", "application/json")
		// json.NewEncoder(w).Encode(record)
	})

	log.Printf("HTTP server starting on port %s", sm.httpPort)
	go func() {
		if err := http.ListenAndServe(sm.httpPort, nil); err != nil {
			log.Printf("HTTP server error: %v", err)
		}
	}()
}

func (sm *ScriptManager) createGUI() {
	myApp := app.New()
	myWindow := myApp.NewWindow("脚本管理器")
	myWindow.Resize(fyne.NewSize(540, 700)) // 调整为原来的60%

	// 脚本列表容器
	scriptContainer := container.NewVBox()

	// 刷新脚本列表的函数
	var refreshScriptList func()
	refreshScriptList = func() {
		scriptContainer.Objects = nil // 清空容器

		scripts := sm.getScripts()
		if len(scripts) == 0 {
			scriptContainer.Add(widget.NewLabel("暂无脚本文件"))
			scriptContainer.Refresh()
			return
		}

		for _, script := range scripts {
			// 为每个脚本创建一行
			currentScript := script // 捕获当前脚本名
			scriptName := script
			if strings.Contains(script, ".default.") {
				scriptName = script + " [默认]"
			}

			nameLabel := widget.NewLabel(scriptName)
			nameLabel.Resize(fyne.NewSize(300, 30))

			// 创建按钮
			var defaultBtn *HoverButton
			if strings.Contains(script, ".default.") {
				defaultBtn = NewHoverButton("取消默认", theme.CancelIcon(), func() {
					sm.setDefaultScript("")
					refreshScriptList()
				})
				defaultBtn.Importance = widget.MediumImportance
			} else {
				defaultBtn = NewHoverButton("设为默认", theme.ConfirmIcon(), func() {
					sm.setDefaultScript(currentScript)
					refreshScriptList()
				})
				defaultBtn.Importance = widget.HighImportance
			}

			deleteBtn := NewHoverButton("删除", theme.DeleteIcon(), func() {
				dialog.ShowConfirm("确认删除",
					fmt.Sprintf("确定要删除脚本 '%s' 吗？", currentScript),
					func(confirmed bool) {
						if confirmed {
							scriptPath := filepath.Join(sm.scriptsDir, currentScript)
							if err := os.Remove(scriptPath); err != nil {
								dialog.ShowError(err, myWindow)
							} else {
								refreshScriptList()
								dialog.ShowInformation("成功", "脚本已删除", myWindow)
							}
						}
					}, myWindow)
			})
			deleteBtn.Importance = widget.MediumImportance

			executeBtn := NewHoverButton("执行", theme.MediaPlayIcon(), func() {
				// 显示loading对话框
				loadingDialog := dialog.NewCustomWithoutButtons("执行中",
					container.NewVBox(
						widget.NewProgressBarInfinite(),
						widget.NewLabel(fmt.Sprintf("正在执行脚本: %s", currentScript)),
						widget.NewLabel("请稍候..."),
					), myWindow)
				loadingDialog.Show()

				// 在goroutine中执行脚本，避免阻塞UI
				go func() {
					record, err := sm.executeScript(currentScript)

					// 关闭loading对话框
					loadingDialog.Hide()

					// 显示执行结果
					message := fmt.Sprintf("脚本: %s\n输出: %s", currentScript, record.Output)
					if err != nil {
						message += fmt.Sprintf("\n错误: %s", record.Error)
					}
					dialog.ShowInformation("执行结果", message, myWindow)
				}()
			})
			executeBtn.Importance = widget.MediumImportance

			// 创建行容器
			row := container.NewHBox(
				nameLabel,
				widget.NewLabel("    "), // 间距
				defaultBtn,
				deleteBtn,
				executeBtn,
			)

			scriptContainer.Add(row)
		}
		scriptContainer.Refresh()
	}

	// 初始加载脚本列表
	refreshScriptList()

	// 添加表头
	header := container.NewHBox(
		widget.NewLabel("脚本名称"),
		widget.NewLabel("                    "), // 间距
		widget.NewLabel("操作"),
	)

	// 上传按钮
	uploadBtn := NewHoverButton("上传脚本", theme.FolderOpenIcon(), func() {
		// 使用系统原生文件对话框
		filename, err := zenity.SelectFile(
			zenity.Title("选择脚本文件"),
			zenity.FileFilters{
				{
					Name:     "脚本文件",
					Patterns: []string{"*.bat", "*.sh", "*.ps1", "*.cmd"},
				},
			},
		)

		if err != nil {
			// 用户取消选择或出错
			if err != zenity.ErrCanceled {
				dialog.ShowError(err, myWindow)
			}
			return
		}

		// 检查是否为支持的脚本类型
		supportedExts := []string{".bat", ".sh", ".ps1", ".cmd"}
		isSupported := false
		for _, ext := range supportedExts {
			if strings.HasSuffix(strings.ToLower(filename), ext) {
				isSupported = true
				break
			}
		}

		if !isSupported {
			dialog.ShowError(fmt.Errorf("支持的脚本类型: .bat, .sh, .ps1, .cmd"), myWindow)
			return
		}

		// 读取文件内容
		data, err := os.ReadFile(filename)
		if err != nil {
			dialog.ShowError(err, myWindow)
			return
		}

		// 获取文件名（不含路径）
		baseName := filepath.Base(filename)
		scriptPath := filepath.Join(sm.scriptsDir, baseName)

		// 根据文件类型设置权限
		var fileMode os.FileMode = 0644
		ext := strings.ToLower(filepath.Ext(baseName))
		if ext == ".sh" {
			// Shell脚本需要执行权限
			fileMode = 0755
		}

		err = os.WriteFile(scriptPath, data, fileMode)
		if err != nil {
			dialog.ShowError(err, myWindow)
			return
		}

		refreshScriptList()
		dialog.ShowInformation("成功", "脚本上传成功", myWindow)
	})
	uploadBtn.Importance = widget.HighImportance

	// 刷新按钮
	refreshBtn := NewHoverButton("刷新", theme.ViewRefreshIcon(), func() {
		refreshScriptList()
	})
	refreshBtn.Importance = widget.MediumImportance

	// 查看历史按钮
	historyBtn := NewHoverButton("查看历史", theme.HistoryIcon(), func() {
		// 读取history目录下的脚本文件夹
		scriptDirs, err := os.ReadDir(sm.historyDir)
		if err != nil {
			dialog.ShowError(err, myWindow)
			return
		}

		var scriptNames []string
		for _, dir := range scriptDirs {
			if dir.IsDir() {
				scriptNames = append(scriptNames, dir.Name())
			}
		}

		if len(scriptNames) == 0 {
			dialog.ShowInformation("提示", "暂无执行历史记录", myWindow)
			return
		}

		historyWindow := myApp.NewWindow("执行历史")
		historyWindow.Resize(fyne.NewSize(900, 600))

		// 打印调试信息
		log.Printf("Creating history window with %d script directories:", len(scriptNames))
		for i, name := range scriptNames {
			log.Printf("  [%d] Directory: '%s' (length: %d)", i, name, len(name))
		}

		// 执行记录详情区域
		detailArea := widget.NewRichTextFromMarkdown("")
		detailArea.Wrapping = fyne.TextWrapWord
		detailScroll := container.NewScroll(detailArea)
		detailScroll.SetMinSize(fyne.NewSize(300, 400))

		// 执行记录列表相关变量
		var currentRecords []string
		var currentScriptDir string
		var recordContainer *fyne.Container
		var recordScroll *container.Scroll

		// 创建记录容器
		recordContainer = container.NewVBox()
		recordScroll = container.NewScroll(recordContainer)
		recordScroll.SetMinSize(fyne.NewSize(200, 400))

		// 刷新记录列表的函数
		refreshRecordList := func() {
			recordContainer.Objects = nil // 清空容器

			if len(currentRecords) == 0 {
				recordContainer.Add(widget.NewLabel("暂无执行记录"))
				recordContainer.Refresh()
				return
			}

			for _, filename := range currentRecords {
				currentFilename := filename // 捕获当前文件名

				// 提取时间信息显示
				displayText := filename
				if len(filename) >= 19 {
					timeStr := filename[:19] // 20240101_150405.000
					dateStr := timeStr[:8]   // 20240101
					timeOnly := timeStr[9:]  // 150405.000

					// 格式化显示：2024-01-01 15:04:05.000
					displayTime := dateStr[:4] + "-" + dateStr[4:6] + "-" + dateStr[6:8] + " " +
						timeOnly[:2] + ":" + timeOnly[2:4] + ":" + timeOnly[4:]
					displayText = "⏰ " + displayTime
				}

				recordBtn := widget.NewButton(displayText, func() {
					// 显示记录详情
					filePath := filepath.Join(currentScriptDir, currentFilename)
					data, err := os.ReadFile(filePath)
					if err != nil {
						dialog.ShowError(err, historyWindow)
						return
					}

					var record ExecutionRecord
					if err := json.Unmarshal(data, &record); err != nil {
						dialog.ShowError(err, historyWindow)
						return
					}

					// 格式化详情内容
					markdown := fmt.Sprintf(`# 执行详情

**脚本名称:** %s

**执行时间:** %s

**执行类型:** %s

**退出代码:** %d

**客户端IP:** %s

## 输出内容:
%s`,
						record.ScriptName,
						record.ExecutedAt.Format("2006-01-02 15:04:05.000"),
						getExecuteTypeDisplay(record.ExecuteType),
						record.ExitCode,
						record.ClientIP,
						"```\n"+record.Output+"\n```")

					if record.Error != "" {
						markdown += fmt.Sprintf("\n\n## 错误信息:\n```\n%s\n```", record.Error)
					}

					detailArea.ParseMarkdown(markdown)
				})
				recordBtn.Alignment = widget.ButtonAlignLeading

				recordContainer.Add(recordBtn)
			}
			recordContainer.Refresh()
		}

		// 创建脚本选择容器（使用按钮列表替代List控件）
		scriptContainer := container.NewVBox()
		var selectedScriptIndex int = -1

		// 为每个脚本创建按钮
		for i, scriptName := range scriptNames {
			currentIndex := i           // 捕获当前索引
			currentScript := scriptName // 捕获当前脚本名

			btn := widget.NewButton(fmt.Sprintf("📁 %s", currentScript), func() {
				selectedScriptIndex = currentIndex
				log.Printf("Selected script: %s (index: %d)", currentScript, currentIndex)

				// 手动触发选择事件
				if selectedScriptIndex < len(scriptNames) {
					selectedScript := scriptNames[selectedScriptIndex]
					currentScriptDir = filepath.Join(sm.historyDir, selectedScript)
					log.Printf("Loading records for script: %s from dir: %s", selectedScript, currentScriptDir)

					// 读取该脚本的执行记录
					files, err := os.ReadDir(currentScriptDir)
					if err != nil {
						log.Printf("Error reading directory %s: %v", currentScriptDir, err)
						dialog.ShowError(err, historyWindow)
						return
					}

					// 清空并重新填充记录列表
					currentRecords = []string{}
					for _, file := range files {
						if !file.IsDir() && strings.HasSuffix(file.Name(), ".json") {
							currentRecords = append(currentRecords, file.Name())
						}
					}

					log.Printf("Found %d records for script %s", len(currentRecords), selectedScript)

					// 按时间倒序排列（最新的在前）
					sort.Slice(currentRecords, func(i, j int) bool {
						return currentRecords[i] > currentRecords[j]
					})

					// 强制刷新列表，确保UI更新
					refreshRecordList()
					detailArea.ParseMarkdown("**选择一个执行记录来查看详情**")
				}
			})
			btn.Alignment = widget.ButtonAlignLeading
			scriptContainer.Add(btn)
		}

		// 创建滚动容器
		scriptScroll := container.NewScroll(scriptContainer)
		scriptScroll.SetMinSize(fyne.NewSize(240, 450))

		// 布局
		leftPanel := container.NewVBox(
			widget.NewLabel(fmt.Sprintf("脚本列表（共%d个）：", len(scriptNames))),
			scriptScroll,
		)
		leftPanel.Resize(fyne.NewSize(250, 500))

		middlePanel := container.NewVBox(
			widget.NewLabel("执行记录："),
			recordScroll,
		)
		middlePanel.Resize(fyne.NewSize(250, 500))

		rightPanel := container.NewVBox(
			widget.NewLabel("详情："),
			detailScroll,
		)

		content := container.NewHSplit(
			container.NewHSplit(leftPanel, middlePanel),
			rightPanel,
		)

		historyWindow.SetContent(content)
		historyWindow.Show()
	})
	historyBtn.Importance = widget.MediumImportance

	// HTTP服务器状态
	// statusLabel := widget.NewLabel(fmt.Sprintf("HTTP服务器运行在端口 %s", sm.httpPort))

	// 布局
	topButtons := container.NewHBox(uploadBtn, refreshBtn, historyBtn)

	// 创建滚动容器包装脚本列表
	scrollContainer := container.NewScroll(scriptContainer)
	scrollContainer.SetMinSize(fyne.NewSize(500, 400))

	content := container.NewVBox(
		// statusLabel,
		topButtons,
		widget.NewSeparator(),
		header,
		widget.NewSeparator(),
		scrollContainer,
	)

	myWindow.SetContent(content)
	myWindow.ShowAndRun()
}

func main() {
	sm := NewScriptManager()

	// 启动HTTP服务器
	sm.startHTTPServer()

	// 创建并显示GUI
	sm.createGUI()
}
